package msu.ml.core;

import msu.ml.data.*;
import weka.classifiers.*;
import java.util.*;
import javax.swing.*;

/**
 * The Experiment Class represents a basic experiment
 * where a classifier is trained with training data and
 * used to classify target data. This data is provided by
 * a data provider and IDataPostProcessors are used to
 * do something meaningful with the results.
 *
 * @author Reginald M Mead
 * @version 1.0
 */
public class GuiExperiment extends SwingWorker<Void, IExperimentInterimResult>
      implements IDataCacheEventListener
{
   
   protected Classifier classifier;
   protected DataProvider dataProvider;
   protected ArrayList<IDataPostProcessor> PostProcessors;
   protected ArrayList<IExperimentInterimResultEventListener> listeners;
   protected String name;
   
   /**
    * Constructs a new Experiment
    */
   public GuiExperiment()
   {
      PostProcessors = new ArrayList<IDataPostProcessor>();
      listeners = new ArrayList<IExperimentInterimResultEventListener>();
   }
   
   
   public void AddExperimentInterimResultListener(IExperimentInterimResultEventListener l)
   {
      this.listeners.add(l);
   }
   
   public void RemoveExperimentInterimResultListener(IExperimentInterimResultEventListener l)
   {
      this.listeners.remove(l);
   }
   
   protected void fireExperimentInterimResultEvent(IExperimentInterimResult result)
   {
      ExperimentInterimResultEvent evt =
            new ExperimentInterimResultEvent(this, result);
      for(IExperimentInterimResultEventListener listener : listeners)
         listener.experimentInterimResultReceived(evt);
   }
   
   /**
    * Add an IDataPostProcessor to this experiment
    *
    * @param processor new IDataPostProcessor
    */
   public void addPostProcessor(IDataPostProcessor processor)
   {
      //processor.setExperiment(this);
      PostProcessors.add(processor);
   }
   
   public void cacheEventReceived(DataCacheEvent evt)
   {
      this.publish(new ExperimentMessage("Cache", evt.getDescription()));
   }
   
   public ArrayList<IDataPostProcessor> getDataPostProcessors()
   {
      return this.PostProcessors;
   }
   
   /**
    * Gets the current classifier being used in this experiment
    *
    * @return current Classifier
    */
   public Classifier getClassifier()
   {
      return classifier;
   }
   
   /**
    * Gets the current Data Provider being used in this experiment
    *
    * @return current Data Provider
    */
   public DataProvider getDataProvider()
   {
      return dataProvider;
   }
   
   public String getName()
   {
      return this.name;
   }
   
   public void trainClassifier(Classifier classifier, DataProvider dataProvider) throws Exception
   {
      NxInstances data = dataProvider.getNextTrainingData();
      publish(new ExperimentMessage("status", "Building Classifier"));
      classifier.buildClassifier(data);
   }
   
   /**
    * Run this Experiment
    */
   @Override
   protected Void doInBackground()
   {
      try
      {
         dataProvider.initialize();

         /*for(IDataPostProcessor processor : PostProcessors)
            processor.beginExperiment();*/
         while(dataProvider.hasMoreData())
         {
            System.out.println("Building Classifier");
            trainClassifier(classifier, dataProvider);
            String [] trainingFiles = dataProvider.getNextTrainingLabels();
            for(IDataPostProcessor processor : PostProcessors)
               processor.beginTraining(trainingFiles);
            
            System.out.println("Classifier Training Complete");
            for(NxInstances instances : dataProvider.getNextTargetData())
            {
               for(IDataPostProcessor processor : PostProcessors)
                  processor.beginInstances(instances.getSourceFile());
               
               publish(new ExperimentMessage("classify",
                     "Classifying " + instances.getSourceFile() + "..."));
               
               for(int i = 0; i < instances.numInstances(); i++)
               {
                  double [] distribution = classifier.distributionForInstance(
                        instances.instance(i));
                  for(IDataPostProcessor processor : PostProcessors)
                     processor.process(instances.instance(i), distribution);
                  publish(new ExperimentData(instances.instance(i)));
               }
               
               for(IDataPostProcessor processor : PostProcessors)
                  processor.endInstances();
            }
         }
         for(IDataPostProcessor processor : PostProcessors)
            processor.endExperiment();
      }
      catch(Exception e)
      {
         System.out.println(e);
         System.out.println(e.getStackTrace());
      }
      
      return null;
   }
   @Override
   protected void process(List<IExperimentInterimResult> results)
   {
      for(IExperimentInterimResult result : results)
      {
         fireExperimentInterimResultEvent(result);
      }
   }
   
   /**
    * Set the classifier to use in this experiment
    *
    * @param newVal Classifier to use in this experiment
    */
   public void setClassifier(Classifier newVal)
   {
      classifier = newVal;
   }
   
   /**
    * Set the DataProvider to use in this experiment
    *
    * @param newVal DataProvider to use in this experiment
    */
   public void setDataProvider(DataProvider newVal)
   {
      dataProvider = newVal;
      dataProvider.getDataCache().AddDataCacheListener(this);
   }
   
   public void setName(String name)
   {
      this.name = name;
   }
   
}
